/**************************************************************************************
* Copyright (c) 2020 Institute of Computing Technology, CAS
* Copyright (c) 2020 University of Chinese Academy of Sciences
*
* NutShell is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*             http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
* FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

package ut_nutshell

import chisel3._
import chisel3.util._

import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import chisel3.stage._
import nutcore._
import top._
import bus.simplebus._
import device._


class DummySbusRWData(var userBits: Int = 16, var idBits: Int = 0, var dname: String = "rwdata") extends Module{
  var io = IO(Flipped(new SimpleBusUC(userBits = userBits, idBits = idBits)))
  var data = RegInit(0.U(64.W))
  var stat = RegInit(0.U.asTypeOf(io.resp.bits.cmd))
  // init
  io.req.ready  := true.B
  io.resp.valid := true.B
  io.resp.bits.cmd   := stat
  io.resp.bits.rdata := 0.U
  if (userBits > 0){
    io.resp.bits.user.get  := 0.U
  }
  if (idBits > 0){
    io.resp.bits.id.get  := 0.U
  }

  // logic
  when(io.isWrite){
    data := io.req.bits.wdata
    stat := SimpleBusCmd.writeLast
    io.resp.bits.cmd := stat
    printf(p"[sb-${dname}] write data: ${io.req.bits}\n")
  }
  when(io.isRead){
    io.resp.bits.rdata := data
    stat := SimpleBusCmd.readLast
    io.resp.bits.cmd := stat
    printf(p"[sb-${dname}]  read data: ${io.resp.bits}, with req: ${io.req.bits}\n")
    data := data + 1.U
  }
}

class XDumyCache extends Module {
  var userBits = 16
  var idBits = 0
  var io = IO(Flipped(new SimpleBusUC(userBits = userBits, idBits = idBits)))

  // make module:
  //                    / fake-ram
  // read: io => cahce |
  //    0 => coh -^     \ fake-mmio

  var fake_ram = Module(new DummySbusRWData(0, idBits, "ram"))
  var fake_mio = Module(new DummySbusRWData(0, idBits, "mio"))
  var cache    = Module(new Cache()(CacheConfig(ro = false, name = "xcache", userBits = userBits, idBits= idBits)))

  // default data
  val cohIn = WireInit(0.U.asTypeOf(new SimpleBusUC))
  cache.io.flush := 0.U

  // connect upstream
  cache.io.in      <> io
  cache.io.out.coh <> cohIn

  // connect downstream
  cache.io.mmio    <> fake_mio.io
  cache.io.out.mem <> fake_ram.io
}

object SBusMain extends App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new DummySbusRWData())
    ))
}

class dummySbusRWDataTest extends AnyFlatSpec with ChiselScalatestTester with Matchers{
  behavior.of("SimpleBusUC")
  it should "test simple read and write" in {
    Console.printf("start test\n")
    //test(new DummySbusRWData(dname="test")) { c =>
    test(new XDumyCache) { c =>
      // init data
      c.io.resp.ready.poke(true.B)
      c.io.req.valid.poke(true.B)
      c.io.req.bits.size.poke(0.U)
      c.io.req.bits.addr.poke(0.U)
      c.io.req.bits.wmask.poke(0.U)
      c.io.req.bits.wdata.poke(0.U)
      c.io.req.bits.user.get.poke(0.U)
      c.io.req.bits.cmd.poke(SimpleBusCmd.read)
      // init step
      c.clock.setTimeout(0)
      c.clock.step(300)
      Console.printf("Init complete!\n")
      // read some data
      for(offset <- 0 to 100){
        // set cmd
        c.io.req.bits.addr.poke((offset*64).U)
        c.clock.step(10)
        println("addr: ", c.io.req.bits.addr.peek(), "  io.req.ready:", c.io.req.ready.peek(), "  io.resp.ready:", c.io.resp.ready.peek())
      }
    }
  }
}

object XDumyCacheMain extends  App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new XDumyCache)
    ))
}

object CacheMain extends App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new Cache()(CacheConfig(ro = false, name = "tcache", userBits = 16)))
    ))
}

object TLBMain extends App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new TLB()(TLBConfig(name = "itlb", userBits = 16, totalEntry = 64)))
    ))
}

object TLBEmbMain extends App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new EmbeddedTLB()(TLBConfig(name = "itlb", userBits = 16, totalEntry = 64)))
    ))
}

object AXI4RAMMain extends App {
  comm.init(args)
  // lanch
  (new ChiselStage).execute(args, Seq(
      ChiselGeneratorAnnotation(() => new AXI4RAM(memByte = 4096))
    ))
}
